package org.momo.rpc.registry.zookeeper;

import lombok.extern.slf4j.Slf4j;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.framework.state.ConnectionState;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
import org.momo.rpc.config.config.registry.ZkRegistryConfig;
import org.momo.rpc.provider.model.ProviderRegistryInfo;

import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Collectors;

@Slf4j
public class Curator {

    public static volatile CuratorFramework zkClient;

    public static final String DELIMITER = "/";

    public static void initClient(ZkRegistryConfig config) {
        if(zkClient == null) {
            synchronized (Curator.class) {
                if(zkClient == null) {
                    // 指数式退避 Retry 3 times, and will increase the sleep time between retries.
                    RetryPolicy retryPolicy = new ExponentialBackoffRetry(config.getRetryBaseSleepTime(), config.getMaxRetryTimes());

                    zkClient = CuratorFrameworkFactory.builder()
                            .connectString(config.getAddress())
                            .connectionTimeoutMs(config.getConnectTimeout())
                            .sessionTimeoutMs(config.getSessionTimeout())
                            .retryPolicy(retryPolicy)
                            .namespace(config.getNamespace())
                            .build();

                    CountDownLatch connectedLatch = new CountDownLatch(1);
                    zkClient.getConnectionStateListenable().addListener((client, state) -> {
                        if (state == ConnectionState.CONNECTED) {
                            log.info("Registry client init success, config: {}", config);
                            connectedLatch.countDown();
                        }
                    });

                    zkClient.start();
                    try {
                        connectedLatch.await();
                    } catch (InterruptedException e) {
                        log.error("");
                    }
                }
            }
        }
    }

    public static void closeClient() {
        zkClient.close();
    }

    public static void createNode(String... path) {
        String absPath = getAbsolutePath(path);
        try {
            if(zkClient.checkExists().forPath(absPath) != null) {
                return;
            }
            zkClient.create()
                    .creatingParentsIfNeeded()
                    .withMode(CreateMode.EPHEMERAL)
                    .forPath(absPath);
        } catch (Exception e) {
            log.error("Zookeeper create node error, the error is {}", e.getMessage());
        }
    }

    public static void removeNode(String... path) {
        String absPath = getAbsolutePath(path);
        try {
            zkClient.delete()
                    .guaranteed()
                    .forPath(absPath);
        } catch (Exception e) {
            log.error("Zookeeper remove node error, the node path is: {}, the error is: {}", absPath, e.getMessage());
        }
    }

    public static boolean checkNodeExist(String... path) {
        String absPath = getAbsolutePath(path);
        try {
            Stat stat = zkClient.checkExists().forPath(absPath);
            return stat == null;
        } catch (Exception e) {
            log.error("Zookeeper create node error, the error is {}", e.getMessage());
        }
        return false;
    }

    public static List<String> getChildrenNode(String... path) {
        String parentPath = getAbsolutePath(path);
        List<String> childrenPath = null;
        try {
            childrenPath = zkClient.getChildren().forPath(parentPath);
        } catch (Exception e) {
            log.error("Zookeeper get children node error, the parent path is: {}, the error is: {}", parentPath, e.getMessage());
        }
        return childrenPath;
    }

    public static void addListener(String absPath, PathChildrenCacheListener listener) {
        try {
            PathChildrenCache cache = new PathChildrenCache(zkClient, absPath, true);
            cache.getListenable().addListener(listener);
            cache.start();
        } catch (Exception e) {
            log.error("Zookeeper add listener error, the error is {}", e.getMessage());
        }
    }

    public static String getAbsolutePath(String ...path) {
        return DELIMITER + String.join(DELIMITER, path);
    }

}
